/*! JRoll v1.0.6 ~ (c) 2015 Author:jlong, Email:jlong@chjtx.com Website:http://www.chjtx.com/JRoll/ */
;(function (window, document, Math) {
  "use strict";
  var rAF = window.requestAnimationFrame || window.webkitRequestAnimationFrame || function(callback) {
    setTimeout(callback, 16);
  };
  var sty = document.createElement('div').style;
  var tSF = ("transform" in sty) ? "transform" : "-webkit-transform";
  var tSD = ("transitionDuration" in sty) ? "transition-duration" : "-webkit-transition-duration";
  var isMobile = /(iPhone)|(Android)|(iPad)/.test(navigator.userAgent);
  var isAndroid = /Android/.test(navigator.userAgent);
  var focusEl = null; //用于暂存focus的表单元素，在android收回软键盘时使其blur
  var normalImport = function(d){ //强制普通模式引入，script添加"JRoll-import='normal'"属性后将忽略exports/define等方式引入
    var s = d.getElementsByTagName("script");
    for (var i=s.length-1; i>-1; i--) {
      if (s[i].getAttribute("JRoll-import") === 'normal') {
        return true;
      }
    }
    return false;
  }(document);

  //计算相对位置，a相对于b的位置
  function computePosition(a, b) {
    var left = 0,
        top = 0;
    while (a) {
      left += a.offsetLeft;
      top += a.offsetTop;
      a = a.offsetParent;
      if (a === b) {
        a = null;
      }
    }
    return {
      left : left,
      top : top
    };
  }

  function JRoll(el, options) {
    this.wrapper = typeof el == 'string' ? document.querySelector(el) : el;
    this.scroller = this.wrapper.children[0];
    
    //计算wrapper相对document的位置
    this.wrapperOffset = computePosition(this.wrapper, document.body);

    //默认选项
    this.options = {
      scroll : true, //使能滑动
      scrollX : true,
      scrollY : true,
      scrollFree : false, //自由滑动
      zoom : false, //使能缩放
      zoomMin : 1, //最小缩放倍数
      zoomMax : 4, //最大缩放倍数
      bounce : false, //回弹
      scrollBar : false, //开启滚动条
      scrollBarFade : false, //滚动条使用渐隐模式
      stopPropagation : false, //禁止冒泡
      momentum : true, //滑动结束平滑过渡
      autoStyle: true, //自动为wrapper和scroller添加样式
      g : 0.0008 //模拟重力加速度,g值越小，运动时间越长
    };

    for (var i in options) {
      this.options[i] = options[i];
    }

    if (this.options.autoStyle) {
      //将wrapper设为relative
      if (window.getComputedStyle(this.wrapper).position === "static") {
        this.wrapper.style.position = "relative";
      }
      this.wrapper.style.overflow = "hidden";
      this.scroller.style.minHeight = "100%";
    }

    this.x = 0;
    this.y = 0;
    this.status = null; //手势状态，null | 'start' | 'end' | 'scroll' | 'zoom'
    this.instance = null; //滑动实例
    this.scrollBarX = null; //x滚动条
    this.scrollBarY = null; //y滚动条

    this._scroll = {
      startX : 0,
      startY : 0,
      endX : 0,
      endY : 0,
      lockX : false,
      lockY : false
    };

    this._zoom = {
      spacing : 0, //两指间间距
      scale : 1,
      startScale : 1
    };

    this._event = {
      "scrollStart" : [],
      "scroll" : [],
      "scrollEnd" : [],
      "zoomStart" : [],
      "zoom" : [],
      "zoomEnd" : []
    };

    //添加scrollStart默认事件
    this._event.scrollStart.push(this._defaultScrollStart);
    //添加scroll默认事件
    this._event.scroll.push(this._defaultScroll);
    //添加scrollEnd默认事件
    this._event.scrollEnd.push(this._defaultScrollEnd);

    this.refresh();

    if (isMobile) {
      this.scroller.addEventListener("touchstart", this._touch.bind(this, this.scroller), false);
      document.addEventListener("touchmove", this._touch.bind(this, this.scroller), false);
      document.addEventListener("touchend", this._touch.bind(this, this.scroller), false);
    } else {
      this.scroller.addEventListener("mousedown", this._touch.bind(this, this.scroller), false);
      document.addEventListener("mousemove", this._touch.bind(this, this.scroller), false);
      document.addEventListener("mouseup", this._touch.bind(this, this.scroller), false);
      //监听鼠标滚轮
      this.scroller.addEventListener("mousewheel", this._wheel.bind(this), false);
    }
    //监听表单事件，以调整窗口变化
    if (isAndroid) {
      this.scroller.addEventListener("focusin", this._focusin.bind(this), false);
      this.scroller.addEventListener("focusout", this._focusout.bind(this), false);
      window.addEventListener("resize", this._resize.bind(this), false);
    }
  }

  JRoll.prototype = {
    refresh : function () {
      this.wrapperWidth = this.wrapper.clientWidth;
      this.wrapperHeight = this.wrapper.clientHeight;

      this.scrollerWidth = Math.round(this.scroller.offsetWidth * this._zoom.scale);
      this.scrollerHeight = Math.round(this.scroller.offsetHeight * this._zoom.scale);

      this.maxScrollX = this.wrapperWidth - this.scrollerWidth;
      this.maxScrollY = this.wrapperHeight - this.scrollerHeight;

      if (this.maxScrollX >= 0) {
        if (!this.options.bounce) {
          this._scroll.lockX = true;
        }
        this.maxScrollX = 0;
      } else {
        this._scroll.lockX = false;
      }
      if (this.maxScrollY >= 0) {
        if (!this.options.bounce) {
          this._scroll.lockY = true;
        }
        this.maxScrollY = 0;
      } else {
        this._scroll.lockY = false;
      }

      this._scroll.endX = this.x;
      this._scroll.endY = this.y;

      //判断是否有滚动条
      if (this.options.scrollBar) {
        var temp;
        //x滚动条
        if (!this._scroll.lockX) {
          if (!this.scrollBarX) {
            temp = this._createScrollBar("JRoll-xbar", "JRoll-xbtn", false);
            this.scrollBarX = temp[0];
            this.scrollBtnX = temp[1];
          }
          this.scrollBarScaleX = this.wrapper.clientWidth / this.scrollerWidth;
          this.scrollBtnX.style.width = Math.round(this.scrollBarX.clientWidth * this.scrollBarScaleX) + "px";
        } else if (this.scrollBarX) {
          this.wrapper.removeChild(this.scrollBarX);
          this.scrollBarX = null;
        }
        //y滚动条
        if (!this._scroll.lockY) {
          if (!this.scrollBarY) {
            temp = this._createScrollBar("JRoll-ybar", "JRoll-ybtn", true);
            this.scrollBarY = temp[0];
            this.scrollBtnY = temp[1];
          }
          this.scrollBarScaleY = this.wrapper.clientHeight / this.scrollerHeight;
          this.scrollBtnY.style.height = Math.round(this.scrollBarY.clientHeight * this.scrollBarScaleY) + "px";
        } else if (this.scrollBarY) {
          this.wrapper.removeChild(this.scrollBarY);
          this.scrollBarY = null;
        }
      }
    },

    _focusin: function(e) {
      var _this = this;
      focusEl = e.target;
      setTimeout(function() {
        var pos, m;
        _this.refresh();
        pos = computePosition(e.target, _this.wrapper);
        m = pos.top + _this.y;
        if (m > 190) {
          _this.scrollTo(_this.x, _this.y - m + 190, 400);
        }
      }, 600);
    },

    _focusout: function(e) {
      var _this = this;
      setTimeout(function() {
        _this.refresh();
        _this.scrollTo(_this.x, _this.y, 400);
      }, 600);  //android有些比较迟钝的浏览器软键盘收起需要600ms
    },

    _resize: function() {
      var _this = this;
      var x = this.maxScrollX, y = this.maxScrollY;
      setTimeout(function() {
        _this.refresh();
        if (x !== _this.maxScrollX || y !== _this.maxScrollY) {
          if (x < _this.maxScrollX || y < _this.maxScrollY) {
            if (focusEl) {
              focusEl.blur();
            }
          }
          _this.scrollTo(_this.x, _this.y, 400);
        }
      }, 600);
    },

    _scrollTo : function (x, y) {
      this.scroller.style[tSF] = "translate(" + x + "px, " + y + "px) translateZ(0) scale(" + this._zoom.scale + ")";
    },

    //供用户调用的scrollTo方法
    scrollTo : function(x, y, timing) {
      this._scroll.endX = this.x = (x >= 0) ? 0 : (x <= this.maxScrollX) ? this.maxScrollX : x;
      this._scroll.endY = this.y = (y >= 0) ? 0 : (y <= this.maxScrollY) ? this.maxScrollY : y;
      if (timing) {
        var _this = this;
        this.scroller.style[tSD] = timing + "ms";
        setTimeout(function(){
          _this.scroller.style[tSD] = "0ms";
        }, timing);
      }
      this._scrollTo(this.x, this.y);
      this._runScrollBar();
    },

    _wheel: function(e) {
      this.scrollTo(this.x, this._compute(this.y + e.wheelDelta, this.maxScrollY));
      this._runScrollBar();
    },

    on : function (event, callback) {
      switch (event) {
      case "scrollStart":
        this._event.scrollStart.push(callback);
        break;
      case "scroll":
        this._event.scroll.push(callback);
        break;
      case "scrollEnd":
        this._event.scrollEnd.push(callback);
        break;
      case "zoomStart":
        this._event.zoomStart.push(callback);
        break;
      case "zoom":
        this._event.zoom.push(callback);
        break;
      case "zoomEnd":
        this._event.zoomEnd.push(callback);
        break;
      }
    },

    _execEvent : function (event) {
      for (var i = 0, l = this._event[event].length; i < l; i++) {
        this._event[event][i].call(this);
      }
    },

    //创建滚动条
    _createScrollBar : function (a, b, isY) {
      var bar,
      btn,
      height;

      bar = document.createElement("div");
      btn = document.createElement("div");
      bar.className = a;
      btn.className = b;

      if (this.options.scrollBar === true) {
        if (isY) {
          bar.style.cssText = "background:#999;position:absolute;top:0;right:0;bottom:0;width:3px;height:100%;overflow:hidden;";
          btn.style.cssText = "background:#000;position:absolute;top:0;left:0;right:0;";
        } else {
          bar.style.cssText = "background:#999;position:absolute;left:0;bottom:0;right:0;height:3px;width:100%;overflow:hidden;";
          btn.style.cssText = "background:#000;height:100%;position:absolute;left:0;top:0;bottom:0;";
        }
      }

      if (this.options.scrollBarFade) {
        bar.style.opacity = 0;
      }

      bar.appendChild(btn);
      this.wrapper.appendChild(bar);

      return [bar, btn];
    },

    //滑动滚动条
    _runScrollBar : function () {
      if (this.options.scrollBar) {
        if (!this._scroll.lockX) {
          var x = Math.round(-this.x * this.scrollBarScaleX);
          this._scrollTo.call({
            scroller : this.scrollBtnX,
            _zoom : {
              scale : 1
            }
          }, x, 0);
        }
        if (!this._scroll.lockY) {
          var y = Math.round(-this.y * this.scrollBarScaleY);
          this._scrollTo.call({
            scroller : this.scrollBtnY,
            _zoom : {
              scale : 1
            }
          }, 0, y);
        }
      }
    },

    //回弹效果
    _bounceFun : function (k, t, a, isY) {
      var s;
      t = t - 16;
      if (t > 0) {
        s = a + k * t * t;
      } else {
        if (isY) {
          this._scroll.endY = this.y = a;
          this._scrollTo(this.x, a);
        } else {
          this._scroll.endX = this.x = a;
          this._scrollTo(a, this.y);
        }
        this._execEvent("scrollEnd");
        return;
      }
      if (isY) {
        this._scroll.endY = this.y = s;
        this._scrollTo(this.x, s);
      } else {
        this._scroll.endX = this.x = s;
        this._scrollTo(s, this.y);
      }
      this._execEvent("scroll");
      rAF(this._bounceFun.bind(this, k, t, a, isY));
    },

    //scrollStart默认事件
    _defaultScrollStart : function () {
      if (this.options.scrollBarFade) {
        this.scrollBarX && (this.scrollBarX.style.opacity = 1);
        this.scrollBarY && (this.scrollBarY.style.opacity = 1);
      }
    },

    //scroll默认事件
    _defaultScroll : function () {
      //滚动条
      this._runScrollBar();
    },

    //回弹
    _bounceEvent : function() {
      var tempX, tempY, k, t = 200;
      tempX = this.x >= 0 ? 0 : (this.x <= this.maxScrollX ? this.maxScrollX : this.x);
      tempY = this.y >= 0 ? 0 : (this.y <= this.maxScrollY ? this.maxScrollY : this.y);

      if (tempX !== this.x) {
        k = (this.x - tempX) / 40000;
        this._bounceFun(k, t, tempX, false);
      }

      if (tempY !== this.y) {
        k = (this.y - tempY) / 40000;
        this._bounceFun(k, t, tempY, true);
      }

      if (tempX === this.x && tempY === this.y) {
        this._execEvent("scrollEnd");
      }
    },

    //scrollEnd默认事件
    _defaultScrollEnd : function () {
      //滚动条
      this._runScrollBar();
      if (this.options.scrollBarFade) {
        this.scrollBarX && this._fade(this.scrollBarX, 2000);
        this.scrollBarY && this._fade(this.scrollBarY, 2000);
      }
    },

    //滚动条渐隐
    _fade : function (bar, time) {
      if ((this.status === "end" || this.status === "start") && time > 0) {
        time = time - 25;
        (time % 100 === 0) && (bar.style.opacity = time / 1000);
      } else {
        return;
      }
      rAF(this._fade.bind(this, bar, time));
    },

    //滑动结束后执行的动作
    _endAction : function (tag) {
      this._scroll.lockX = false;
      this._scroll.lockY = false;
      if (!tag) {
        this.instance = null;
      }
      this.status = tag || "end";
      if (this.options.bounce) {
        this._bounceEvent();
      } else {
        this._execEvent("scrollEnd");
      }
    },

    _step : function () {
      var tempX, tempY;

      if (this.status !== "scroll") {
        if (this.status !== "start") {
          this._endAction();
        } else {
          this._endAction("start");
        }
        return;
      }

      if (this.options.scrollFree && this.timeX <= 0 && this.timeY <= 0) {
        this._endAction();
        return;
      }

      //处理x方向
      if (!this._scroll.lockX) {
        if (this.timeX <= 0 && !this.options.scrollFree) {
          this._endAction();
          return;
        }

        if (this.timeX <= 0 && this.options.scrollFree) {
          this.speedX = 0;
        } else {
          this.speedX = +(this.directionX + this.timeX * this.options.g);
        }
        this.x = this._scroll.endX = Math.round(this._scroll.endX + this.speedX * 16);
        tempX = this._compute(this.x, this.maxScrollX);
        if (tempX !== this.x) {
          this.x = this._scroll.endX = (this.options.bounce && (this.x - tempX > 10) ? parseInt(this.directionX + 25, 10) + tempX : tempX);
          if (!this.options.scrollFree) {
            this.status = "end";
          }
        }
        this.timeX = this.timeX - 16;
      }

      //处理y方向
      if (!this._scroll.lockY) {
        if (this.timeY <= 0 && !this.options.scrollFree) {
          this._endAction();
          return;
        }

        if (this.timeY <= 0 && this.options.scrollFree) {
          this.speedY = 0;
        } else {
          this.speedY = +(this.directionY + this.timeY * this.options.g);
        }
        this.y = this._scroll.endY = Math.round(this._scroll.endY + this.speedY * 16);
        tempY = this._compute(this.y, this.maxScrollY);
        if (tempY !== this.y) {
          this.y = this._scroll.endY = (this.options.bounce && (this.y - tempY > 10) ? parseInt(this.directionY + 25, 10) + tempY : tempY);
          if (!this.options.scrollFree) {
            this.status = "end";
          }
        }
        this.timeY = this.timeY - 16;
      }

      this._scrollTo(this.x, this.y);
      this._execEvent("scroll");

      rAF(this._step.bind(this));
    },

    //计算x,y的值
    _compute : function (val, max) {
      if (val > 0) {
        if (this.options.bounce && (val > 10)) {
          return Math.round(val / 5);
        } else {
          return 0;
        }
      }

      if (val < max) {
        if (this.options.bounce && (val < (max - 10))) {
          return Math.round(max + ((val - max) / 5));
        } else {
          return max;
        }
      }

      return val;
    },

    //触发事件
    _touch : function (el, e) {
      var type = e.type,
      t = e.touches || [e];

      if (this.options.stopPropagation) {
        e.stopPropagation();
      }

      switch (type) {
      case "touchstart":
      case "mousedown":
        this.status = "start";
        this.instance = el;

        if (t.length === 1) { //单指
          this.startTime = Date.now();
          this.startPositionX = this._scroll.startX = t[0].pageX;
          this.startPositionY = this._scroll.startY = t[0].pageY;
        } else { //双指
          this.scroller.style["-webkit-transform-origin"] = "0 0";

          var c1 = Math.abs(t[0].pageX - t[1].pageX),
          c2 = Math.abs(t[0].pageY - t[1].pageY);

          this._zoom.spacing = Math.sqrt(c1 * c1 + c2 * c2);
          this._zoom.startScale = this._zoom.scale;

          this.originX = Math.abs(t[0].pageX + t[1].pageX) / 2 - this.wrapperOffset.left - this.x;
          this.originY = Math.abs(t[0].pageY + t[1].pageY) / 2 - this.wrapperOffset.top - this.y;
        }

        break;
      case "touchmove":
      case "mousemove":
        e.preventDefault();

        if (!this.instance) {
          return;
        }

        //判断手势
        if (this.status === "start") {
          if (t.length === 1 && this.options.scroll) {
            //判断向哪个方向滑动，如果已有一个方向被锁定则不用判断
            if (!this.options.scrollFree && !(this._scroll.lockX || this._scroll.lockY)) {
              if (Math.abs(t[0].pageX - this._scroll.startX) > Math.abs(t[0].pageY - this._scroll.startY)) {
                this._scroll.lockY = true; //不允许y方向滑动
              } else {
                this._scroll.lockX = true; //不允许x方向滑动
              }
            }
            if (!this.options.scrollX) {
              this._scroll.lockX = true;
            }
            if (!this.options.scrollY) {
              this._scroll.lockY = true;
            }
            this._execEvent("scrollStart");
            this.status = "scroll";
          } else if (this.options.zoom) {
            this._execEvent("zoomStart");
            this.status = "zoom";
          }
          return;
        }

        //执行滑动
        if (this.status === "scroll") {
          if (!this._scroll.lockX) {
            this.x = t[0].pageX - this._scroll.startX + this._scroll.endX;
            this.x = this._compute(this.x, this.maxScrollX);
          }
          if (!this._scroll.lockY) {
            this.y = t[0].pageY - this._scroll.startY + this._scroll.endY;
            this.y = this._compute(this.y, this.maxScrollY);
          }

          this._scrollTo(this.x, this.y);
          this._execEvent("scroll");

          if (Date.now() - this.startTime > 200) {
            this.startTime = Date.now() - 50;
            this.startPositionX = t[0].pageX;
            this.startPositionY = t[0].pageY;
          }
          if (!this._scroll.lockX) {
            this.distanceX = t[0].pageX - this.startPositionX;
          }
          if (!this._scroll.lockY) {
            this.distanceY = t[0].pageY - this.startPositionY;
          }

          return;
        }

        //执行缩放
        if (this.status === "zoom") {
          var c1 = Math.abs(t[0].pageX - t[1].pageX),
          c2 = Math.abs(t[0].pageY - t[1].pageY),
          spacing = Math.sqrt(c1 * c1 + c2 * c2),
          scale = spacing / this._zoom.spacing * this._zoom.startScale,
          lastScale;

          if (scale < this.options.zoomMin) {
            scale = this.options.zoomMin;
          } else if (scale > this.options.zoomMax) {
            scale = this.options.zoomMax;
          }

          lastScale = scale / this._zoom.startScale;

          this.x = Math.round(this.originX - this.originX * lastScale + this._scroll.endX);
          this.y = Math.round(this.originY - this.originY * lastScale + this._scroll.endY);
          this._zoom.scale = scale;

          this._scrollTo(this.x, this.y);
          this._execEvent("zoom");

          return;
        }

        break;
      case "touchend":
      case "mouseup":
        //释放鼠标
        if (this.status === "start") {
          this.instance = null;
          this.status = "end";
        }
        //滑动结束
        if (this.status === "scroll") {
          this._scroll.endX = this.x;
          this._scroll.endY = this.y;

          if (this.options.momentum) {
            var runX = false,
            runY = false;
            this.duration = Date.now() - this.startTime;

            if (this.duration < 300) {
              if (!this._scroll.lockX) {
                this.speedX = this.distanceX / this.duration;
                this.timeX = Math.abs(this.speedX / this.options.g);
                if (this.speedX < 0) {
                  this.directionX = "-";
                } else {
                  this.directionX = "+";
                }

                runX = true;
              }

              if (!this._scroll.lockY) {
                this.speedY = this.distanceY / this.duration;
                this.timeY = Math.abs(this.speedY / this.options.g);
                if (this.speedY < 0) {
                  this.directionY = "-";
                } else {
                  this.directionY = "+";
                }

                runY = true;
              }

              if (runX || runY) {
                rAF(this._step.bind(this));
              } else {
                this._endAction();
              }
            } else {
              this._endAction();
            }
          } else {
            this._endAction();
          }

          return;
        }

        //缩放结束
        if (this.status === "zoom") {

          if (this._zoom.scale > this.options.zoomMax) {
            this._zoom.scale = this.options.zoomMax;
          } else if (this._zoom.scale < this.options.zoomMin) {
            this._zoom.scale = this.options.zoomMin;
          }

          this.refresh();

          if (this.x > 0) {
            this._scroll.endX = this.x = 0;
          } else if (this.x < this.maxScrollX) {
            this._scroll.endX = this.x = this.maxScrollX;
          }
          if (this.y > 0) {
            this._scroll.endY = this.y = 0;
          } else if (this.y < this.maxScrollY) {
            this._scroll.endY = this.y = this.maxScrollY;
          }

          this._scrollTo(this.x, this.y);

          this.status = "end";
          this._execEvent("zoomEnd");

          return;
        }

        break;
      }
    }
  };

  if (typeof module !== 'undefined' && module.exports && !normalImport) {
    module.exports = JRoll;
  } else if (typeof define === 'function' && !normalImport) {
    define(function() {return JRoll});
  } else {
    window.JRoll = JRoll;
  }

})(window, document, Math);
